﻿using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using K9Nano.LayIM.Events;
using K9Nano.LayIM.Exceptions;
using K9Nano.LayIM.Hubs;
using K9Nano.LayIM.Models;
using K9Nano.LayIM.Stores;
using K9Nano.LayIM.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace K9Nano.LayIM.Internal
{
    internal class ChatManager : IChatManager
    {
        private readonly IChatHubContext _chatHubContext;
        private readonly ILogger _logger;
        private readonly IChatFriendStore _friendStore;
        private readonly IChatRoomStore _roomStore;
        private readonly IChatAppOnlineStaffCache _appOnlineStaffCache;
        private readonly LayIMOptions _options;

        public ChatManager(IChatHubContext chatHubContext,
            ILogger<ChatManager> logger,
            IChatFriendStore friendStore,
            IChatRoomStore roomStore,
            IOptions<LayIMOptions> options,
            IChatAppOnlineStaffCache appOnlineStaffCache)
        {
            _chatHubContext = chatHubContext;
            _logger = logger;
            _friendStore = friendStore;
            _roomStore = roomStore;
            _appOnlineStaffCache = appOnlineStaffCache;
            _options = options.Value;
        }

        public async Task OnlineAsync(OnlineNotification online, CancellationToken cancellation)
        {
            await _friendStore.OnlineAsync(online.UserId, cancellation);

            // Notify all online friends
            var myFriends = await _friendStore.GetOnlineFriendsAsync(online.UserId, cancellation);
            foreach (var friend in myFriends)
            {
                await _chatHubContext.NotifyFriendOnlineAsync(friend, online.UserId, cancellation);
            }

            var room = await _roomStore.GetAsync(online.App, online.UserId, cancellation);
            if (room != null)
            {
                var user = await _friendStore.GetUserAsync(online.UserId, cancellation);
                if (user != null)
                {
                    await _chatHubContext.NotifyRoomUserOnlineAsync(room.Id, online.ConnectionId, user.username, cancellation);
                }
            }

            if (online.Roles.Contains(_options.StaffRoleName))
            {
                // For staff
                await _appOnlineStaffCache.OnlineAsync(online);

                if (_appOnlineStaffCache.TryDequeue(online.App, out var staffRoom))
                {
                    await StaffJoinRoomAsync(staffRoom, online);
                }
            }

            // TODO Send latest messages of this room
        }

        public async Task OfflineAsync(OfflineNotification offline, CancellationToken cancellation)
        {
            await _friendStore.OfflineAsync(offline.UserId, cancellation);

            // Notify all online friends
            var myFriends = await _friendStore.GetOnlineFriendsAsync(offline.UserId, cancellation);
            foreach (var friend in myFriends)
            {
                await _chatHubContext.NotifyFriendOfflineAsync(friend, offline.UserId, cancellation);
            }

            var room = await _roomStore.GetAsync(offline.App, offline.UserId, cancellation);
            if (room != null)
            {
                var user = await _friendStore.GetUserAsync(offline.UserId, cancellation);
                if (user != null)
                {
                    await _chatHubContext.NotifyRoomUserOfflineAsync(room.Id, user.username, cancellation);
                }
            }

            if (offline.Roles.Contains(_options.StaffRoleName))
            {
                // For staff
                await _appOnlineStaffCache.OfflineAsync(offline);

                // TODO Assign the online customers of this staff to a new free staff
            }
        }

        public async Task AddFriendAsync(string userId, string friendId, string groupId, CancellationToken cancellation)
        {
            Guard.NotEmpty(userId, nameof(userId));
            Guard.NotEmpty(friendId, nameof(friendId));
            Guard.NotEmpty(groupId, nameof(groupId));

            var friend = await _friendStore.GetUserAsync(friendId, cancellation);
            if (friend == null)
            {
                throw new LayIMException($"User {friendId} is not exist");
            }

            await _friendStore.AddFriendToGroupAsync(friendId, groupId, cancellation);

            // Notify

            await _chatHubContext.AddFriendAsync(userId, new LayIMAddFriend
            {
                id = friendId,
                username = friend.username,
                avatar = friend.avatar,
                groupid = groupId,
                sign = friend.sign
            });
        }

        public async Task<LayIMUser> GetUserAsync(string userId, CancellationToken cancellation)
        {
            return await _friendStore.GetUserAsync(userId, cancellation);
        }

        public async Task<LayIMChatRoom> GetRoomAsync(string app, string userId, CancellationToken cancellation)
        {
            var room = await _roomStore.GetAsync(app, userId, cancellation);

            await TryAssignOnlineStaffAsync(room, cancellation);

            // else: Waiting until a staff got online

            return room;
        }

        public async ValueTask TryAssignOnlineStaffAsync(LayIMChatRoom room, CancellationToken cancellation)
        {
            var staff = _appOnlineStaffCache.Next(room);
            if (staff == null)
            {
                // Enqueue to waiting for a staff
                _appOnlineStaffCache.Enqueue(room);
            }
            else
            {
                await StaffJoinRoomAsync(room, staff);
            }
        }

        private async Task StaffJoinRoomAsync(LayIMChatRoom room, OnlineNotification staff)
        {
            await _chatHubContext.JoinHubGroupAsync(room.Id, staff.ConnectionId);
            await _roomStore.AddMemberAsync(room.Id, staff.UserId, default);
            await _chatHubContext.AddGroupAsync(staff.UserId, new LayIMAddGroup
            {
                id = room.Id,
                groupname = room.Name,
                avatar = room.Avatar
            });

            //var added = await _friendStore.TryAddRoomToAppGroupAsync(room.Id, staff.UserId, staff.App, default);
            //if (added != null)
            //{
            //    // New friend notice
            //    await _chatHubContext.AddFriendAsync(staff.UserId, added);
            //}
        }
    }
}